//== SAGITTARIUS ============================================================================

//== FILEDOC =========================================================================

/** @file codegen.h
  * @brief codegen
  */

//== BEGINNING OF CODE ===============================================================

#ifndef __SAGUTTARUIS__LLVM_CODEGEN__H_
#define __SAGUTTARUIS__LLVM_CODEGEN__H_

//== INCLUDES ========================================================================

#include "sagittarius/core/SagittariusCore.h"
#include "sagittarius/parser/pAST.h"
#include "sagittarius/symtab/symtab.h"
#include "sagittarius/compiler/compiler.h"
#include "sagittarius/builtins/builtins.h"
#include "sagittarius/builtins/builtinfun.h"
#include "sagittarius/mod/module.h"
#include "sagittarius/backend/llvm/support.h"
#include "sagittarius/backend/llvm/llvmsupport.h"
#include "sagittarius/backend/llvm/llvm_interf.h"
#include "sagittarius/backend/llvm/llvm_types.h"

#ifdef __cplusplus

//LLVM
#include "llvm/Support/DataTypes.h"
#include "llvm/DerivedTypes.h"
#include "llvm/IRBuilder.h"
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Analysis/Verifier.h"

#include "llvm/ExecutionEngine/ExecutionEngine.h"
#include "llvm/ExecutionEngine/JIT.h"
#include "llvm/ExecutionEngine/MCJIT.h"
#include "llvm/ExecutionEngine/GenericValue.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/raw_os_ostream.h"


namespace Sagittarius {
    
    llvm::Function::arg_iterator get_arg(llvm::Function* f);
    
    class CodeGen_Types {
        public:
            
            /// init internal functions
            void init_internals();
            
            //CodeGen_Types();
            
            std::vector<llvm::Type*> make_obj_header();
            
            llvm::FunctionType* make_fun_tp(compiledFun* f);
            
            // -- Constants --
            
            llvm::Constant* getConstVoidPtr(void* p);
            
            llvm::Constant* getConstPVal(sg_value* v);
            
            llvm::Constant* getConstPPVal(sg_value** v);
            
            llvm::Constant* getConstSym(symcode s);
            
            llvm::Value* getPVal(llvm::Value* v);
            
            llvm::Constant* getPValNull();
            
            llvm::Value* getPPVal(llvm::Value* v);
            
            llvm::Constant* getUInt64Const(uint64 n);
            
            llvm::Constant* getInt64Const(int64 n);
            
            llvm::Value* castToPVal(llvm::Value* obj);
            
            // name-value pairs
            llvm::Value* getConstSymbol(sg_name_val* m);
            
            // function types
            llvm::FunctionType* make_fun_tp_from_call(uint64 nargs);
            
            // fun values
    
            llvm::Value* getConstFunVal(sg_fun_val* f);
            
            void checkFunValTp(llvm::Value* fv);
            
            llvm::Value* castToFunVal(llvm::Value* obj);
            
            void callDumpConstFunVal(sg_fun_val* f);
            
            void funVal_StoreFP(sg_fun_val* f, llvm::Value* fp);
            
            llvm::StructType* getFunVal_IRDef();
            
            // get the function pointer (as void*) from a function val
            llvm::Value* getFunValVd(sg_fun_val* fv);
            
            llvm::Value* getFunValInlineVd(llvm::Value* fval);
            
            // get the function pointer (as void*) from a function val
            llvm::Value* getFunValFPtr(sg_fun_val* fv);
            
            llvm::Value* getFunValInlineFPtr(llvm::Value* fv, llvm::FunctionType* ft);
            
            // symbols
//             llvm::Value* getSymbolBoundObj(sg_name_val* m);
            llvm::Value* getConstNameValPtr(sg_name_val* m);
            
            void createTypes();
            
            // lists
            void emit_list_reserve(llvm::Value* list, uint64 n);
            
            void emit_list_store_by_index(llvm::Value* list, llvm::Value* obj, uint64 n);
            
            /// Global mappings
            void setup_mappings(llvm::ExecutionEngine* EE);
            
        protected:
            llvm::IRBuilder<>* builder;
            llvm::Module* mod;
            
            /// Symbol table
            symtab* st;
            
            //-- Types --
            
            llvm::PointerType* pval;
            llvm::PointerType* ppval;
            
            /// ints
            llvm::Type* i8_tp;
            llvm::Type* i16_tp;
            llvm::Type* i32_tp;
            llvm::Type* i64_tp;
            
            /// void ptr
            llvm::Type* void_tp;
            llvm::Type* pvoid;
            
            /// pointer type as uint
            llvm::Type* lp_tp;
            
            /// Type that symcode uses (uint64)
            llvm::Type* symcode_tp;
            
            /// Objects
            llvm::StructType* val;
            uint64 val_header_off;
            // object header
            std::vector<llvm::Type*> obj_header;
            // name/value
            llvm::StructType* name_val_tp;
            llvm::Type* pname_val_tp;
            
            // symbol instance
            llvm::StructType* symbol_inst_tp;
            llvm::Type* psymbol_inst_tp;
            
            // sg_fun_val
            llvm::StructType* funval;
            llvm::Type* pfunval;
            
            // sg_list_val
            llvm::StructType* listval_tp;
            llvm::Type* plistval_tp;
            
            // aux functions
            /// f_dump_fun_val
            llvm::Function* f_dump_fun_val;
            llvm::FunctionType* f_dump_fun_val_tp;
            
            /// f_check_fvaltype
            llvm::Function* f_check_fvaltype;
            llvm::FunctionType* f_check_fvaltype_tp;
            
            /// f_list_reserve
            llvm::Function* f_list_reserve;
            llvm::FunctionType* f_list_reserve_tp;
            
            /// f_list_get_by_index
            llvm::Function* f_list_get_by_index;
            llvm::FunctionType* f_list_get_by_index_tp;
            
            /// f_list_store_by_index
            llvm::Function* f_list_store_by_index;
            llvm::FunctionType* f_list_store_by_index_tp;
    };
    
    class CodeGen_LocalSymbols : public CodeGen_Types {
        public:
            typedef CodeGen_Types CodeGen_LocalSymbols_Super;
            
            /// init internal functions
            void init_internals();
            
            virtual void createTypes();
            
            /// Access the list element of locals structure
            llvm::Value* emit_locals_access_list(llvm::Value* locals);
            
            /// Create a symbol instance
            llvm::Value* emit_create_symbol_instance(sg_name_val* m);
            
            /// Emit initialization of structure to hold local vars
            llvm::Value* emit_create_locals(sg_fctx_val* z, llvm::Value* parent);
            
            /// Resolve a symbol in the context hierarchy
            llvm::Value* fetch_symbol(sg_name_val* m, sg_fctx_val* z, llvm::Value* locals);
            
            void emit_store_local(llvm::Value* locals, llvm::Value* obj, sg_name_val* m);
            
            /// Bind an object to a symbol
            void emit_bind_obj_to_sym(llvm::Value* syminst, llvm::Value* obj, std::string symname);
            
            /// Global mappings
            void setup_mappings(llvm::ExecutionEngine* EE);
            
        protected:
            /// local vars
            llvm::StructType* locals_tp;
            llvm::Type* plocals_tp;
            
            uint64 locals_list_offset;
            
            /// f_create_locals
            llvm::Function* f_create_locals;
            llvm::FunctionType* f_create_locals_tp;
            
            /// f_create_sym_inst
            llvm::Function* f_create_sym_inst;
            llvm::FunctionType* f_create_sym_inst_tp;
            
            /// f_create_sym_inst_s
            llvm::Function* f_create_sym_inst_s;
            llvm::FunctionType* f_create_sym_inst_s_tp;
            
            /// f_bind_obj_to_sym
            llvm::Function* f_bind_obj_to_sym;
            llvm::FunctionType* f_bind_obj_to_sym_tp;
            
            /// f_locals_get_parent
            llvm::Function* f_locals_get_parent;
            llvm::FunctionType* f_locals_get_parent_tp;
            
            /// f_get_local_by_index
            llvm::Function* f_get_local_obj_by_index;
            llvm::FunctionType* f_get_local_obj_by_index_tp;
            
            /// f_get_local_by_index
            llvm::Function* f_get_local_sym_by_index;
            llvm::FunctionType* f_get_local_sym_by_index_tp;
            
            /// f_get_local_by_index
            llvm::Function* f_store_local_by_index;
            llvm::FunctionType* f_store_local_by_index_tp;
    };
    
    class CodeGen_UserFun : public CodeGen_LocalSymbols {
        public:
            typedef CodeGen_LocalSymbols CCodeGen_UserFun_Super;
            
            llvm::FunctionType* make_fun_tp_from_call(uint64 nargs);
            llvm::FunctionType* make_fun_tp(compiledFun* f);
    };
    
    class CodeGen_ModImport : public CodeGen_UserFun {
        public:
            typedef CodeGen_UserFun CodeGen_ModImport_Super;
            
            /// init internal functions
            void init_internals();
            
            /// Import expression
            llvm::Value* emit_import_expr(compiledSrc* c, sg_fctx_val* z, parserExp* x, llvm::Value* obj);
            
            /// Single import statement
            llvm::Value* emit_single_import(compiledSrc* c, sg_fctx_val* z, singleImport* g);
            
            /// Import statement
            void emit_import(compiledSrc* c, sg_fctx_val* z, importStmt* d);
            
            /// Global mappings
            void setup_mappings(llvm::ExecutionEngine* EE);
            
        protected:
            /// f_fetch_mod
            llvm::Function* f_fetch_mod;
            llvm::FunctionType* f_fetch_mod_tp;
        
            /// f_do_faccessr
            llvm::Function* f_do_faccessr;
            llvm::FunctionType* f_do_faccessr_tp;
    };
    
    class CodeGen_Toplevel : public CodeGen_ModImport {
        public:
            typedef CodeGen_ModImport Super;
            
            /// Constructor
            CodeGen_Toplevel(sg_module* m);
            /// Destructor
            ~CodeGen_Toplevel();
            
            llvm::Type* get_builtin_fun_ret_tp(sg_fun_val* f);
            
            void install_builtin_funs(builtin_fun_ctx* bf);
            
            /// init internal functions
            virtual void init_internals();
            
            void do_fprint_int(uint64 i);
            
            // -- Utils
            
            std::string name_from_sym(symcode s);
            
//             llvm::StructType* get_locals_type(sg_fctx_val* z);
            
            /// Emit a local symbol
            void instance_symbol(compiledSrc* c, sg_name_val* li, llvm::Value* pscope);
            
            llvm::Value* emit_get_sym_inst_sym(llvm::Value* syminst);
            
            /// Get the object associated with a symbol
            llvm::Value* emit_get_sym_pobj(llvm::Value* syminst);
            
            /// Get the ppobj
            llvm::Value* emit_symbol_instance_get_ppobj(llvm::Value* syminst);
            
            // -- Function emission --
            
            void emit_funs(compiledSrc* c);
            
            llvm::Value* emit_fun(compiledSrc* c, sg_fctx_val* z, compiledFun* f);
            
            // -- Node emission --
            
            /// Emit assignment
            llvm::Value* emit_do_assn(llvm::Value* lhs, llvm::Value* rhs);
            
            /// Lvalue
            llvm::Value* emit_lval(compiledSrc* c, sg_fctx_val* z, compiledNode* x);
            
            /// Node
            virtual llvm::Value* emit_node(compiledSrc* c, sg_fctx_val* z, compiledNode* x);
            
            /// Generate IR for a source fragment
            void generate();
            
            /// Global mappings
            virtual void setup_mappings(llvm::ExecutionEngine* EE);
            
            /// run
            void run();
            
        protected:
        /// Builtin functions
        //builtin_fun_ctx* bf;
        
        sg_module* msrc;
        
        //-- LLVM internals --
        
        //llvm::StructType* obj_t;
        
        //-- Functions --
        
        llvm::Function* fmalloc;
        /// Type for malloc
        llvm::FunctionType* fmalloct;
        llvm::Function* ffree;
        
        /// Main fun
        llvm::Function *fmain;
        
        /// do call
        llvm::Function* f_do_call0;
        llvm::FunctionType* f_do_call0_tp;
        llvm::Function* f_do_call1;
        llvm::FunctionType* f_do_call1_tp;
        
        /// do assn
        llvm::Function* f_do_assn;
        llvm::FunctionType* f_do_assn_tp;
        
        /// f_do_faccessl
        llvm::Function* f_do_faccessl;
        llvm::FunctionType* f_do_faccessl_tp;
        
        /// f_create_int
        llvm::Function* f_create_int;
        llvm::FunctionType* f_create_int_tp;
        
        /// f_create_uint
        llvm::Function* f_create_uint;
        llvm::FunctionType* f_create_uint_tp;
        
        /// f_create_str
        llvm::Function* f_create_str;
        llvm::FunctionType* f_create_str_tp;
        
        /// f_rt_call_check
        llvm::Function* f_rt_call_check;
        llvm::FunctionType* f_rt_call_check_tp;
        
        /// f_dump_uint64
        llvm::Function* f_dump_uint64;
        llvm::FunctionType* f_dump_uint64_tp;
    };
}
#endif

#endif
